<?php

/**
 * @todo Load many to one problem
 * @todo Dirty flag problem
 * @todo Save and delete routine
 *
 */
class Adept_Db_ActiveRecord
{

    const LAZY = 'lazy';
    const FOREIGN_KEY = 'foreign_key';
    const CLASS_NAME = 'class';

    protected $_table;
    protected $_tableName;
    protected $_classProperties;
    protected $_primaryKey;

    /**
     * Example:
     * <pre>
     *    protected $_oneToMany = array('cars' => 
     *    array(
     *        'foreign_key' => 'teacher_id', 
     *        'class' => 'Student', 
     *        'lazy' => true, 
     *        'cascade_delete' => true, 
     *        'cascade_save' => true));
     * </pre>
     *
     * @var array
     */
    protected $_oneToMany = array();
    protected $_manyToOne = array();

    protected $_connection;

    protected $id;

    protected $_isNew = true;
    protected $_isDeleted = false;

    public function __construct($tableName = null, $connection = null)
    {
        $this->_connection = Adept_Db_Factory::getConnection($connection);

        if (!is_null($tableName)) {
            $this->_setTable($tableName);
        } else {
            if (!isset($this->_tableName)) {
                $this->_tableName = $this->_guessTableName();
            }
            $this->_setTable($this->_tableName);
        }

    }

    protected function _guessTableName()
    {
        $className = get_class($this);
        $lastPart = end(explode('_', $className));
        return Adept_Util_String::toUnderScoresNotation($lastPart);
    }

    protected function _guessClassProperties(Adept_Db_Table $table)
    {
        $columnsInfo = $table->getTableInfo()->getColumnsInfo();

        $classProperties = array();

        foreach ($columnsInfo as $column) {
            $columnName = $column->getName();
            $proprtyName = Adept_Util_String::toCamelNotation($columnName);

            if ($column->isPrimaryKey()) {
                $this->_primaryKey = $columnName;
                $classProperties['id'] = array('column' => $columnName);
            } else {
                if (property_exists($this, $proprtyName)) {
                    $classProperties[$proprtyName] = array('column' => $columnName);
                }
            }
        }
        return $classProperties;
    }

    protected function _prepareRecord()
    {
        $result = array();
        foreach ($this->_classProperties as $property => $options) {
            if ($this->_table->getTableInfo()->hasColumn($options['column'])) {
                $result[$options['column']] = $this->$property;
            }
        }
        return $result;
    }


    protected function _setTable($tableName)
    {
        $this->_tableName = $tableName;
        $this->_table = new Adept_Db_Table($tableName);

        if (!isset($this->_classProperties)) {
            $this->_classProperties = $this->_guessClassProperties($this->_table);
        }

    }

    public function getId()
    {
        return $this->id;
    }

    public function setId($id)
    {
        $this->id = $id;
    }

    protected function _loadOneToMany($record)
    {
        foreach ($this->_oneToMany as $property => $options) {

            if (isset($options[self::LAZY]) && $options[self::LAZY]) {
                $id = $this->getId();
                $where = $options[self::FOREIGN_KEY] . " = '{$id}'";
                $ids = Adept_Db_ActiveRecord::findIds($options[self::CLASS_NAME], $where);

//                $proxyCollection = array();
//                foreach ($ids as $id) {
//                    $proxyCollection[] = new Adept_Db_ActiveRecord_Proxy($id, $options[self::CLASS_NAME]);
//                }
                $proxyCollection = new Adept_Db_ActiveRecord_Collection();
                $proxyCollection->setIds($ids);
                
                $this->$property = $proxyCollection;

            } else {
                if (!isset($options[self::CLASS_NAME])) {
                    throw new Adept_Db_ActiveRecord_Exception('No class option defined');
                }
                if (!isset($options[self::FOREIGN_KEY])) {
                    throw new Adept_Db_ActiveRecord_Exception('No foreign key option defined');
                }
                $id = $this->getId();
                $where = $options[self::FOREIGN_KEY] . " = '{$id}'";
                $this->$property = self::find($options[self::CLASS_NAME], $where);
            }

        }
    }

    protected function _loadManyToOne($record)
    {
        foreach ($this->_manyToOne as $property => $options) {
            //            if (isset($options[self::LAZY]) && $options[self::LAZY]) {

            $id = $record[$options[self::FOREIGN_KEY]];

            $proxyObject = new Adept_Db_ActiveRecord_Proxy($id, $options[self::CLASS_NAME]);
            $this->$property = $proxyObject;

            //            } else {
            //                $foreignKey = $record[$options[self::FOREIGN_KEY]];
            //                $this->$property = self::findOne($options[self::CLASS_NAME], "id = '{$foreignKey}'");
            //            }
        }
    }

    /**
     * @throws Adept_Db_ActiveRecord_Exception 
     */
    public function loadFromRecord($record)
    {
        foreach ($this->_classProperties as $property => $options) {
            if (isset($record[$options['column']])) {
                $this->$property = $record[$options['column']];
            }

            $this->_loadOneToMany($record);
            $this->_loadManyToOne($record);
        }
    }

    public function loadById($id)
    {
        $record = $this->_table->selectById($id);
        if (!empty($record)) {
            $this->loadFromRecord($record);
            $this->_isNew = false;
            $this->_isDeleted = false;
            return true;
        } else {
            return false;
        }
    }

    public function save()
    {
        $record = $this->_prepareRecord();
        if ($this->_isNew || $this->_isDeleted) {
            $insertId = $this->_table->insert($record);
            $this->setId($insertId);
            $this->_isNew = false;

        } else {
            $this->_table->updateById($record, $this->getId());
        }
        $this->_isDeleted = false;
    }

    public function delete()
    {
        if ($this->getId() && !$this->_isNew && !$this->_isDeleted)    {
            $result = (boolean) $this->_table->deleteById($this->getId());
            $this->_isDeleted = true;
            return $result;
        }
        return false;
    }

    protected function _find($where, $order, $count, $offset)
    {
        $recordSet = $this->_table->select($where, $order, $count, $offset);
        $result = array();
        $className = get_class($this);
        foreach ($recordSet as $record) {
            $item = new $className();
            $item->loadFromRecord($record);
            $result[$item->getId()] = $item;
        }
        return $result;
    }

    protected function _findOne($where, $order, $offset)
    {
        $record = $this->_table->selectFirst($where, $order, $offset);
        if ($record != null) {
            $className = get_class($this);
            $item = new $className();
            $item->loadFromRecord($record);
            return $item;
        } else {
            return null;
        }
    }

    protected function _findIds($where = null, $order = null, $count = null, $offset = null)
    {
        $select = $this->_connection->getSelectQuery();

        $select->from($this->_tableName)
        ->column($this->_primaryKey);

        if (!is_null($where)) {
            $select->where($where);
        }
        if (!is_null($order)) {
            $select->order($order);
        }
        if (!is_null($count) || !is_null($offset)) {
            $select->limit($count, $offset);
        }

        return $this->_connection->newStatement($select)->getRecordSet()->getOneColumn();

    }

    /**
     * Find active records by conditions
     *
     * @param string $className
     * @param string $where
     * @param string $order
     * @param int $count
     * @param int $offset
     * @return array Collection of founded active records
     */
    static public function find($className, $where = null, $order = null, $count = null, $offset = null)
    {
        $instance = new $className();
        return $instance->_find($where, $order, $count, $offset);
    }

    static public function findOne($className, $where = null, $order = null, $offset = null)
    {
        $instance = new $className();
        return $instance->_findOne($where, $order, $offset);
    }

    static public function findById($className, $id)
    {
        $instance = new $className();
        if ($instance->loadById($id)) {
            return $instance;
        } else {
            return null;
        }
    }

    static public function findIds($className, $where = null, $order = null, $count = null, $offset = null)
    {
        $instance = new $className();
        return $instance->_findIds($where, $order, $count, $offset);
    }

}
